package org.fatewa.engine.genius.utils;

import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.conditions.AbstractChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import core.MultipartData;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.fatewa.engine.design.base.PrimaryKeyProvider;
import org.fatewa.engine.design.genius.GenericHooks;
import org.fatewa.engine.genius.annotations.Public;
import org.fatewa.engine.genius.annotations.Relation;
import org.fatewa.engine.genius.base.Operator;
import org.fatewa.engine.utils.Qs;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 通用 Controller 逻辑抽取
 *
 * @author fatewa
 */
@Slf4j
public class GenericUtils {

    /**
     * 支持的查询方法集合
     * 将 {@link Operator} 的字段名称映射为 Mybatis Plus
     * {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    private static final Map<String, String> QUERY_MAPPER = new HashMap<>();

    /**
     * 缓存需要被调用的条件拼接方法，提高查询时调用效率
     */
    private static final Map<String, Method> METHOD_MAPPER = new HashMap<>();

    static {
        QUERY_MAPPER.put("equals", "eq");
        QUERY_MAPPER.put("like", "like");
        QUERY_MAPPER.put("lessThan", "lt");
        QUERY_MAPPER.put("lessEquals", "le");
        QUERY_MAPPER.put("greatThan", "gt");
        QUERY_MAPPER.put("greatEquals", "ge");

        // 缓存需要被调用的条件拼接方法，提高查询时拼接效率
        for (Method method : AbstractChainWrapper.class.getDeclaredMethods()) {
            // 方法名称
            String name = method.getName();
            long count = QUERY_MAPPER.values()
                    .stream()
                    .filter(it -> it.equals(name))
                    .count();

            // 仅使用动态拼接的函数
            if (count > 0 && method.getParameterCount() == 3) {
                METHOD_MAPPER.put(name, method);
            }
        }
    }

    /**
     * 由于 spring 在接受参数时会将类型转换为 {@link Map} 进行处理，故需要引用
     * {@link MultipartData} 的灵活转换能力，获得实体类的真实类
     *
     * @param clazz 真实类型
     * @param tmp   Spring 接收参数
     * @param <T>   目标类型
     * @param <E>   Spring 接收到的类型，通常为 Map
     * @return 真实类型
     */
    @SneakyThrows
    public static <T, E> T newEntity(Class<T> clazz, E tmp) {
        MultipartData multipartData = new MultipartData();
        multipartData.parties(tmp);
        return multipartData.get(clazz, true);
    }

    /**
     * 对 hook 进行排序
     *
     * @param hooks hook 集合
     * @return 升序排序后的 hook 集合
     */
    public static Collection<GenericHooks<?, ?>> orderedHooks(Collection<GenericHooks<?, ?>> hooks) {
        if (null == hooks) {
            return new ArrayList<>();
        }
        return hooks.stream()
                .filter(Objects::nonNull)
                .sorted(Comparator.comparingInt(GenericHooks::order))
                .collect(Collectors.toList());
    }

    public static <P, E> P getPrimaryKey(PrimaryKeyProvider<P> provider, E entity) {
        MultipartData multipartData = new MultipartData();
        multipartData.parties(entity);
        return multipartData.getPart(provider.key(), provider.type(), true);
    }

    public static <P> P primaryKey(PrimaryKeyProvider<P> provider, P id) {
        MultipartData multipartData = new MultipartData();
        multipartData.include(provider.key(), id);
        return multipartData.getPart(provider.key(), provider.type(), true);
    }


    /**
     * 判断当前字段是否公开了操作
     *
     * @param field 字段
     * @param type  操作类型
     * @return 当前字段是否公开了操作
     */
    private static boolean accessible(Field field, String type) {
        final Public isPublic = field.getDeclaredAnnotation(Public.class);
        if (null == isPublic) {
            return false;
        }
        switch (type) {
            case "eq":
                return isPublic.eq();
            case "le":
                return isPublic.le();
            case "ge":
                return isPublic.ge();
            case "lt":
                return isPublic.lt();
            case "bet":
                return isPublic.bet();
            case "like":
                return isPublic.like();
            case "gt":
                return isPublic.gt();
            default:
                return false;
        }
    }


    /**
     * 通用查询方法
     *
     * @param request     请求对象
     * @param entityClass 目标实体
     * @param service     实体 service
     * @param <T>         实体类型
     * @return 分页数据
     */
    public static <T> IPage<T> query(HttpServletRequest request, Class<T> entityClass, IService<T> service) {

        // 最终返回的分页数据
        IPage<T> page = new Page<>();

        // 操作集合
        Map<String, Operator> operators = Qs.parse(request);

        // 用户传入的分页页码
        String current = request.getParameter("current");

        // 用户传入的分页大小
        String size = request.getParameter("size");

        if (Strings.isNotEmpty(current)) {
            int value = Integer.parseInt(current);
            page.setCurrent(value);
        }

        if (Strings.isNotEmpty(size)) {
            int value = Integer.parseInt(size);
            page.setSize(value);
        }

        // 过滤实体中不存在的字段
        operators.keySet().removeIf(it -> {
            try {
                entityClass.getDeclaredField(it);
                return false;
            } catch (NoSuchFieldException e) {
                log.warn("Query param <{}> dropped because of not exists in {}", it, entityClass);
                return true;
            }
        });

        QueryChainWrapper<T> wrapper = service.query();

        for (String key : operators.keySet()) {
            Operator operator = operators.get(key);
            Field[] fields = Operator.class.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                try {
                    if (QUERY_MAPPER.containsKey(field.getName())) {
                        String method = QUERY_MAPPER.get(field.getName());
                        // 获取查询操作值
                        Object value = field.get(operator);
                        // 值为为空，跳过字段处理
                        if (null == value) {
                            continue;
                        }
                        // 类型为字符串时，为空跳过
                        if (value instanceof String) {
                            CharSequence sequence = (CharSequence) value;
                            if (Strings.isEmpty(sequence)) {
                                continue;
                            }
                            if (NumberUtil.isNumber(sequence)) {
                                value = NumberUtil.parseFloat(sequence.toString());
                            }
                        }
                        Method targetMethod = METHOD_MAPPER.get(method);
                        // 当操作符所对应的方法可以查询到时，进行方法调用
                        if (null != targetMethod) {
                            targetMethod.setAccessible(true);
                            // 调用之前会检查当前字段是否标注了 @Public
                            boolean accessible = accessible(entityClass.getDeclaredField(key), method);
                            // 三个参数的方法调用, 除了 between 之外的其他基础操作都可以这样调用
                            targetMethod.invoke(wrapper, accessible, key, value);
                            targetMethod.setAccessible(false);
                        }
                    }
                } catch (IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
                    continue;
                }
                field.setAccessible(false);
            }
        }

        return wrapper.page(page);
    }

    public static RequestMappingInfo
    getRelationMappingInfo(Relation relations,
                           StringBuilder basePath,
                           String targetPath, GetMapping get, PostMapping post, DeleteMapping delete, PatchMapping patch) {
        String sep = "/";
        Function<String[], String> join = (strings -> String.join(sep, strings));

        boolean isGet = null != get && (relations.page() || relations.single());

        BiFunction<String[], StringBuilder, String> buildPath = (p, sb) -> {
            sb.append(join.apply(p)
                    .replace("[TARGET]", targetPath));
            return sb.toString();
        };

        // 处理 Get 请求
        if (isGet) {
            String getMapping = buildPath.apply(get.value(), basePath);
            // 单独判断 single 接口
            boolean isSingle = getMapping.endsWith("/{id}");

            if (isSingle && !relations.single()) {
                return null;
            }

            RequestMappingInfo.Builder builder = RequestMappingInfo
                    .paths(getMapping);

            return builder
                    .methods(RequestMethod.GET)
                    .build();
        }

        boolean isDelete = null != delete && relations.delete();

        // 处理删除请求
        if (isDelete) {
            RequestMappingInfo.Builder builder = RequestMappingInfo
                    .paths(buildPath.apply(delete.value(), basePath));

            return builder
                    .methods(RequestMethod.DELETE)
                    .build();
        }


        boolean isUpdate = null != patch && relations.update();

        // 处理更新请求
        if (isUpdate) {

            RequestMappingInfo.Builder builder = RequestMappingInfo
                    .paths(buildPath.apply(patch.value(), basePath));

            return builder
                    .methods(RequestMethod.PATCH)
                    .build();
        }

        boolean isPost = null != post && relations.create();

        // 处理更新请求
        if (isPost) {
            RequestMappingInfo.Builder builder = RequestMappingInfo
                    .paths(buildPath.apply(post.value(), basePath));

            return builder
                    .methods(RequestMethod.POST)
                    .build();
        }

        return null;
    }

    /**
     * 封装事务快速提交的方法
     *
     * @param callable 中间执行的函数
     * @param context  Spring 上下文
     */
    public static void transaction(BiConsumer<DataSourceTransactionManager, TransactionStatus> callable, ApplicationContext context) {
        Assert.notNull(callable, "transaction initialization failed.");
        DataSourceTransactionManager manager = context.getBean(DataSourceTransactionManager.class);
        Assert.notNull(manager, "transaction initialization failed.");

        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        // 开启事务
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus transaction = manager.getTransaction(definition);

        try {
            callable.accept(manager, transaction);
            // 提交事务
            manager.commit(transaction);
        } catch (Exception e) {
            // 回滚事务
            manager.rollback(transaction);
            throw e;
        }
    }
}
